﻿#if SILVERLIGHT
using System.Collections.ObjectModel;
using System.Windows.Browser;
using System.Windows.Controls;
#endif

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ServiceModel.DomainServices.Client;
#if SILVERLIGHT
using AbstraX.ClientInterfaces;
#else
using AbstraX.ServerInterfaces;
#endif
using System.Threading;
using Microsoft.Practices.Unity;
using AbstraX.Contracts;
using AbstraX.Services;
using AbstraX.ViewModels;
using AbstraX;
using System.ServiceModel;
using System.Net;
using System.Windows;
using System.Windows.Threading;
using System.ComponentModel;
using AbstraX.ViewModels.ModelTree;
using AbstraX.ViewModels.BindingsTree;
using System.Diagnostics;
using System.ServiceModel.Dispatcher;
using AbstraX.BuildWizard;
using BuildInvokeOperation = AbstraX.BuildWizard.BuildInvokeOperation;

namespace AbstraX
{
    public delegate void OnShowUIHandler(string uiComponentURL, string parms);
    public delegate void OnBuildMessageHandler(string message);
    public delegate void OnBuildCompleteHandler();
    public delegate void OnErrorHandler(string errorMessage);
    public delegate void OnPercentCompleteHandler(float percentComplete);

    public static class ProviderLoader
    {
        private static RootsCollection roots;
        private static Dictionary<string, DomainContext> contexts;
        private static bool loading;
        private static Queue<string> providerQueue;
        private static Assembly currentProviderAssembly;
        private static BuildInvokeOperation buildInvokeOperation;
        private static EventsStartOperation eventsStartOperation;

        public static event OnShowUIHandler OnShowUI;
        public static event OnBuildMessageHandler OnBuildMessage;
        public static event OnPercentCompleteHandler OnPercentComplete;
        public static event OnBuildCompleteHandler OnBuildComplete;
        public static event OnErrorHandler OnError;
#if SILVERLIGHT
        private static EventsContext eventsContext;
        private static DispatcherTimer eventsTimer;

        public class RootsCollection : ObservableCollection<IRoot>, IRootsCollection
#else
        public class RootsCollection : List<IRoot>, IRootsCollection
#endif
        {
        }

        public static IBuildProxy BuildProxy { get; set; }

#if SILVERLIGHT
        public static Label StatusLabel { get; set; }

#endif

        public static void SetStatus(string status)
        {
#if SILVERLIGHT

            if (StatusLabel != null)
            {
                StatusLabel.Content = status;
            }
#endif
        }

#if SILVERLIGHT

        public static void StopEvents()
        {
            if (eventsTimer != null)
            {
                eventsTimer.Stop();
            }
        }

        public static EventsStartOperation StartEvents()
        {
            var waiting = false;

            eventsTimer = new DispatcherTimer();
            eventsContext = new EventsContext();

            eventsStartOperation = new EventsStartOperation();

            SetStatus("Starting event service listener");

            eventsTimer.Tick += (sender2, e2) =>
            {
                if (!waiting) // Monitor.TryEnter not working
                {
                    waiting = true;

                    try
                    {
                        var operation = eventsContext.GetMessages();

                        operation.Completed += (sender3, e3) =>
                        {
                            try
                            {
                                var messages = operation.Value;

                                if (messages != null)
                                {
                                    messages.ToList().ForEach(m =>
                                    {
                                        switch (m.Message)
                                        {
                                            case Message.ShowUI:
                                                {
                                                    var parms = m.ReadFrom();

                                                    OnShowUI((string)parms[0], (string)parms[1]);

                                                    break;
                                                }
                                            case Message.BuildMessage:
                                                {
                                                    var parms = m.ReadFrom();

                                                    OnBuildMessage((string)parms[0]);

                                                    break;
                                                }
                                            case Message.PercentComplete:
                                                {
                                                    var parms = m.ReadFrom();

                                                    OnPercentComplete((float)parms[0]);

                                                    break;
                                                }
                                            case Message.Error:
                                                {
                                                    var parms = m.ReadFrom();

                                                    OnError((string) parms[0]);

                                                    break;
                                                }
                                            case Message.BuildComplete:
                                                {
                                                    var parms = m.ReadFrom();

                                                    buildInvokeOperation.SetCompleted();
                                                    OnBuildComplete();

                                                    break;
                                                }
                                        }
                                    });
                                }

                                if (eventsStartOperation != null)
                                {
                                    eventsStartOperation.SetCompleted();
                                    eventsStartOperation = null;
                                }
                            }
                            catch (Exception ex)
                            {
                                Debugger.Break();
                            }

                            waiting = false;
                        };
                    }
                    catch (Exception ex)
                    {
                        Debugger.Break();
                    }
                }
            };

            eventsTimer.Interval = new TimeSpan(0, 0, 0, 0, 1000); // one second
            eventsTimer.Start();      
  
            return eventsStartOperation;
        }
#endif
        public static IUnityContainer Container
        {
            get
            {
                return IoC.Container;
            }
        }

        public static Dictionary<string, DomainContext> Contexts
        {
            get 
            { 
                return ProviderLoader.contexts; 
            }
        }

        public static RootsCollection ProviderRoots
        {
            get
            {
                if (roots == null)
                {
                    roots = new RootsCollection();
                }

                return roots;
            }
        }

        public static void Initialize()
        {
            contexts = new Dictionary<string, DomainContext>();

            IoC.RegisterType<IProviderClientService, ProviderClientService>();
            IoC.RegisterType(typeof(IModelViewModel), typeof(ModelViewModel));
            IoC.RegisterType(typeof(IBindingsViewModel), typeof(BindingsViewModel));
        }

        public static void LoadProviders(IEnumerable<string> providers)
        {
            providerQueue = new Queue<string>();

            SetStatus("Loading providers...");

            foreach (var provider in providers)
            {
                providerQueue.Enqueue(provider);
            }

            if (providerQueue.Count > 0)
            {
                var timer = new DispatcherTimer();

                timer.Interval = new TimeSpan(0, 0, 0, 0, 1);

                timer.Tick += (object s, EventArgs args) =>
                {
                    if (!loading)
                    {
                        var client = new WebClient();
                        var provider = providerQueue.Dequeue();

                        loading = true;

                        SetStatus("Loading " + provider);

                        client.OpenReadCompleted += new OpenReadCompletedEventHandler(OnAssemblyOpened);
                        client.OpenReadAsync(new Uri(provider, UriKind.Relative));

                        if (providerQueue.Count == 0)
                        {
                            timer.Stop();
                        }
                    }
                };

                timer.Start();
            }
        }

        private static void OnAssemblyOpened(object sender, OpenReadCompletedEventArgs e)
        {
            var rootType = string.Empty;

            var assemblyPart = new AssemblyPart();
            var assembly = assemblyPart.Load(e.Result);
            var rootFound = false;
            var domainFound = false;
            var entityQueryFound = false;

            currentProviderAssembly = assembly;

            foreach (Type type in assembly.GetTypes())
            {
                var interfaces = type.GetInterfaces().AsQueryable().Cast<Type>();

                if (interfaces.Any(t => t.Name == "IRoot") && !interfaces.Any(t => t.Name == "IRootSurrogate"))
                {
                    rootFound = true;
                    rootType = type.FullName.Replace(".Client.", ".");

                    foreach (Type type2 in assembly.GetTypes())
                    {
                        if (type2.BaseType != null && type2.BaseType.Name == "DomainContext" && type2.Name != "BindingsTreeContext")
                        {
                            DomainContext context;
                            var nameSpace = type.Namespace.Replace(".Client", "");

                            domainFound = true;

                            if (contexts.ContainsKey(nameSpace))
                            {
                                context = contexts[nameSpace];
                            }
                            else
                            {
                                context = Activator.CreateInstance(type2) as DomainContext;
                                contexts.Add(nameSpace, context);

                                var channelFactoryProperty = context.DomainClient.GetType().GetProperty("ChannelFactory");

                                if (channelFactoryProperty == null)
                                {
                                    throw new InvalidOperationException("There is no 'ChannelFactory' property on the DomainClient.");
                                }

                                var factory = (ChannelFactory)channelFactoryProperty.GetValue(context.DomainClient, null);

                                factory.Endpoint.Binding.SendTimeout = new TimeSpan(0, 5, 0);
                            }

                            foreach (MethodInfo methodInfo in type2.GetMethods())
                            {
                                if (methodInfo.ReturnType.Name == "EntityQuery`1")
                                {
                                    var typeParm = methodInfo.ReturnType.GetGenericArguments().AsQueryable().Cast<Type>().FirstOrDefault();

                                    if (typeParm.FullName == rootType)
                                    {
                                        var query = methodInfo.Invoke(context, new object[] { });
                                        var loadMethod = type2.GetMethods().AsQueryable().Cast<MethodInfo>().FirstOrDefault(m => m.Name == "Load");
                                        var loadGenericMethod = loadMethod.MakeGenericMethod(new Type[] { typeParm });
                                        var loadOp = (LoadOperation)loadGenericMethod.Invoke(context, new object[] { query });

                                        entityQueryFound = true;

                                        SetStatus("Loading roots for " + type.Name);

                                        loadOp.Completed += new EventHandler(loadOp_Completed);

                                        break;
                                    }
                                }
                            }
                        }
                    }

                    break;
                }
            }

            if (!rootFound)
            {
                Debugger.Break();
            }
            else if (!domainFound)
            {
                Debugger.Break();
            }
            else if (!entityQueryFound)
            {
                Debugger.Break();
            }
        }

        private static void loadOp_Completed(object sender, EventArgs e)
        {
            var loadOp = (LoadOperation)sender;
            var entity = (Entity)loadOp.Entities.FirstOrDefault();
            var type = entity.GetType();
            var clientType = currentProviderAssembly.GetType(type.Namespace + ".Client." + type.Name);

            var root = Activator.CreateInstance(clientType, new object[] { entity }) as IRoot;

            foreach (DomainContext context in ProviderLoader.Contexts.Values)
            {
                if (context.GetType().Namespace + ".Entities.Client" == root.GetType().Namespace)
                {
#if SILVERLIGHT
                    root.Context = context;
#endif
                }
            }

            if (roots == null)
            {
                roots = new RootsCollection();
            }

            roots.Add(root);

            SetStatus("");

            loading = false;
        }

        public static BuildInvokeOperation BuildMiddleLayer(string id)
        {
            if (ProviderLoader.BuildProxy == null)
            {
                ProviderLoader.BuildProxy = ProviderLoader.Container.Resolve<IBuildProxy>();
            }

            if (buildInvokeOperation != null)
            {
                if (!buildInvokeOperation.IsComplete)
                {
                    throw new Exception(string.Format("Illegal build request during a build started at {0}", buildInvokeOperation.BuildStarted));
                }
            }

            buildInvokeOperation = new BuildInvokeOperation();

            SetStatus("Requesting build.");

            BuildProxy.BuildMiddleLayer(id);

            return buildInvokeOperation;
        }

        public static bool BuildInProgress
        {
            get
            {
                return buildInvokeOperation != null && !buildInvokeOperation.IsComplete;
            }
        }

        public static void UnInitialize()
        {
        }
    }
}

//#endif